package com.simpou.commons.persistence.jpa.dao.impl;

import com.simpou.commons.persistence.jpa.entity.TestDaoEntity_;
import com.simpou.commons.persistence.jpa.entity.TestDaoEntity;
import com.simpou.commons.persistence.Parameters;
import com.simpou.commons.persistence.common.Transaction;
import com.simpou.commons.persistence.jpa.model.SingularAttributeEntry;
import static com.simpou.commons.utils.lang.Randoms.*;
import com.simpou.commons.utils.pagination.PageLimits;
import com.simpou.commons.utils.tests.TestRunner;
import com.simpou.commons.utils.tests.annot.ClassTest;
import com.simpou.commons.utils.tests.annot.Repeat;
import com.simpou.commons.utils.tests.annot.TestOrder;

import lombok.Getter;

import org.junit.After;
import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Persistence;
import javax.persistence.metamodel.SingularAttribute;


@ClassTest(AbstractTypedJpaDAOImpl.class)
@RunWith(TestRunner.class)
public class AbstractTypedJpaDAOImplITCase {
    private static TestJpaDAOImpl dao = new TestJpaDAOImpl();
    private static List<TestDaoEntity> entities = new ArrayList<TestDaoEntity>();

    @Before
    public void setUp() {
        dao.getTransaction().begin();
    }

    @After
    public void tearDown() throws Exception {
        final Transaction transaction = dao.getTransaction();

        if (transaction.isActive()) {
            transaction.commit();
        }
    }

    @Test
    @TestOrder(1)
    @Repeat(10)
    public void testCreate() throws Exception {
        TestDaoEntity entity = new TestDaoEntity();
        entity.fillRandom();
        dao.create(entity);
        entities.add(entity);
    }

    @Test
    @TestOrder(2)
    public void testCount_0args() throws Exception {
        Long count = dao.count();
        assertEquals(entities.size(), count.intValue());
    }

    @Test
    @TestOrder(2)
    public void testGetList_0args() throws Exception {
        List<TestDaoEntity> list = dao.getList();

        for (TestDaoEntity entity : entities) {
            assertTrue(list.contains(entity));
        }
    }

    @Test
    @TestOrder(3)
    public void testUpdate() throws Exception {
        TestDaoEntity randomEntity = getRandomEntity();
        final String oldName = randomEntity.getName();
        final String newName = oldName + "new";
        randomEntity.setName(newName);
        dao.update(randomEntity);
        dao.flush();

        TestDaoEntity entityRet = dao.getSingle(TestDaoEntity.class,
                randomEntity.getId());
        assertEquals(newName, entityRet.getName());
    }

    @Test
    public void testGetSingle_Serializable() throws Exception {
        //testUpdate
    }

    @Test
    @TestOrder(3)
    public void testGetList_PageLimits() throws Exception {
        List<TestDaoEntity> listAll = dao.getList();
        PageLimits limits = getLimits(listAll.size());
        List<TestDaoEntity> listLimits = dao.getList(limits);
        assertEquals(limits.getSize(), listLimits.size());

        for (int i = limits.getFirst(), j = 0; i < (limits.getLast() + 1);
                i++, j++) {
            assertEquals(listAll.get(i), listLimits.get(j));
        }
    }

    @Test
    @TestOrder(3)
    public void testNamedExecute() throws Exception {
        TestDaoEntity entity = getRandomEntity();
        String oldName = entity.getName();
        String newName = oldName + "new";
        Long id = entity.getId();
        int result = dao.namedExecute("updateName", newName, oldName);
        assertEquals(1, result);

        TestDaoEntity entityRet = dao.getNamedSingle("findByName", newName);
        assertEquals(entity, entityRet);
    }

    @Test
    @TestOrder(3)
    public void testExecute() throws Exception {
        TestDaoEntity entity = getRandomEntity();
        String oldName = entity.getName();
        String newName = oldName + "new";
        String className = TestDaoEntity.class.getSimpleName();
        Long id = entity.getId();
        String queryUpdate = "UPDATE " + className +
            " e SET e.name=?1 WHERE e.id=?2";
        String queryRetrieve = "SELECT e FROM " + className +
            " e WHERE e.id=?1";
        int result = dao.execute(queryUpdate, newName, id);
        assertEquals(1, result);

        TestDaoEntity entityRet = dao.getSingle(queryRetrieve, id);
        assertEquals(newName, entityRet.getName());
    }

    @Test
    public void testGetSingle_String_ObjectArr() throws Exception {
        //testExecute
    }

    @Test
    @TestOrder(3)
    public void testGetNamedSingle() throws Exception {
        //testNamedExecute
    }

    @Test
    public void testCount_String_ObjectArr() throws Exception {
        //testGetList_String_ObjectArr
    }

    @Test
    @TestOrder(3)
    public void testGetList_String_ObjectArr() throws Exception {
        Long count;
        List<TestDaoEntity> list;
        final int countExp = 3;
        final String className = TestDaoEntity.class.getSimpleName();
        TestDaoEntity entity1 = entities.get(0);
        TestDaoEntity entity2 = entities.get(1);
        TestDaoEntity entity3 = entities.get(2);
        final String newNameSuffix = "**NewName**";
        final String newName = getString(10, 20, true) + newNameSuffix;
        entity1.setName(newName);
        entity2.setName(newName);
        entity3.setName(newName);
        dao.update(entity1);
        dao.update(entity2);
        dao.update(entity3);

        final List<SingularAttributeEntry<TestDaoEntity, ?>> listAttbEqual = new ArrayList<SingularAttributeEntry<TestDaoEntity, ?>>();
        listAttbEqual.add(new SingularAttributeEntry<TestDaoEntity, String>(
                TestDaoEntity_.name, newName));

        final List<SingularAttributeEntry<TestDaoEntity, String>> listAttbLike = new ArrayList<SingularAttributeEntry<TestDaoEntity, String>>();
        listAttbLike.add(new SingularAttributeEntry<TestDaoEntity, String>(
                TestDaoEntity_.name, newNameSuffix));

        final String selectQuery = "SELECT e FROM " + className +
            " e WHERE e.name=?1";
        final PageLimits limits = getLimits(countExp);

        // list all
        list = dao.getList(selectQuery, null, newName);
        assertEquals(countExp, list.size());
        checkName(list, newName);

        // limit list
        list = dao.getList(selectQuery, limits, newName);
        assertEquals(limits.getSize(), list.size());
        checkName(list, newName);

        // list attbs equal
        list = dao.getList(listAttbEqual, null);
        assertEquals(countExp, list.size());
        checkName(list, newName);

        // list attbs like
        list = dao.getList(null, listAttbLike);
        assertEquals(countExp, list.size());
        checkName(list, newName);

        // count attbs
        count = dao.count(listAttbEqual, null);
        assertEquals(countExp, count.intValue());

        // limit list attbs
        list = dao.getList(limits, listAttbEqual, null);
        assertEquals(limits.getSize(), list.size());
        checkName(list, newName);

        // count
        count = dao.count("SELECT count(e) FROM " + className +
                " e WHERE e.name=?1", newName);
        assertEquals(countExp, count.intValue());
    }

    @Test
    public void testGetList_3args_1() throws Exception {
        //testGetList_String_ObjectArr
    }

    @Test
    @TestOrder(2)
    public void testGetNamedList() throws Exception {
        List<TestDaoEntity> list = dao.getNamedList("findAll", null);

        for (TestDaoEntity entity : entities) {
            assertTrue(list.contains(entity));
        }
    }

    @Test
    @TestOrder(3)
    public void testGetSingle_List_List() throws Exception {
        TestDaoEntity entity = getRandomEntity();
        final Long id = entity.getId();
        List<SingularAttributeEntry<TestDaoEntity, ?>> listAttbEqual = new ArrayList<SingularAttributeEntry<TestDaoEntity, ?>>();

        // sem RAW não funciona, dado que "id" pertence à super classe.
        SingularAttribute singularAttribute = TestDaoEntity_.id;
        SingularAttributeEntry<TestDaoEntity, Long> singAttbEntry = new SingularAttributeEntry<TestDaoEntity, Long>(singularAttribute);
        singAttbEntry.setValue(id);
        listAttbEqual.add(singAttbEntry);

        TestDaoEntity entityRet = dao.getSingle(listAttbEqual, null);
        assertNotNull(entityRet);
        assertEquals(id, entityRet.getId());
    }

    @Test
    public void testCount_List_List() throws Exception {
        //testGetList_String_ObjectArr
    }

    @Test
    public void testGetList_3args_2() throws Exception {
        //testGetList_String_ObjectArr
    }

    @Test
    @TestOrder(4)
    public void testDelete_GenericType() throws Exception {
        TestDaoEntity entityRet;
        TestDaoEntity entity = getRandomEntity();
        Long id = entity.getId();
        entityRet = dao.getSingle(TestDaoEntity.class, id);
        assertNotNull(entityRet);
        dao.delete(entity);
        entityRet = dao.getSingle(TestDaoEntity.class, id);
        assertNull(entityRet);
        entities.remove(entity);
    }

    @Test
    @TestOrder(4)
    public void testDelete_Serializable() throws Exception {
        TestDaoEntity entityRet;
        TestDaoEntity entity = getRandomEntity();
        Long id = entity.getId();
        entityRet = dao.getSingle(TestDaoEntity.class, id);
        assertNotNull(entityRet);
        dao.delete(TestDaoEntity.class, id);
        entityRet = dao.getSingle(TestDaoEntity.class, id);
        assertNull(entityRet);
        entities.remove(entity);
    }

    @Test
    @TestOrder(5)
    public void testDeleteAll() throws Exception {
        dao.deleteAll();

        final Long count = dao.count();
        assertEquals(0, count.intValue());
    }

    @Test(expected = EntityNotFoundException.class)
    @TestOrder(6)
    public void testDelete_Serializable_error() throws Exception {
        dao.delete(TestDaoEntity.class, 1L);
    }

    @Test
    @TestOrder(6)
    public void testGetSingle_Serializable_noResult() throws Exception {
        TestDaoEntity entity = dao.getSingle("SELECT e FROM " +
                TestDaoEntity.class.getSimpleName() + " e WHERE e.id=?1", 1L);
        assertNull(entity);
    }

    @Test
    public void testGetTransaction() throws Exception {
        //setUp
        TestDaoEntity entity = new TestDaoEntity();
        entity.fillRandom();
        assertNull(entity.identity());
        dao.create(entity);
        dao.flush();
        assertNotNull(entity.identity());
        dao.getTransaction().rollback();
        assertNull(dao.getSingle(TestDaoEntity.class, entity.identity()));
        dao.getTransaction().rollback(); //transação não ativa não gera erros
    }

    @Test
    public void testFlush() {
        //testUpdate
    }

    private void checkName(List<TestDaoEntity> list, String name) {
        for (TestDaoEntity entity : list) {
            assertEquals(name, entity.getName());
        }
    }

    private TestDaoEntity getRandomEntity() {
        return entities.get(getInteger(0, entities.size() - 1));
    }

    private TestDaoEntity getRandomEntityUpdated(TestDaoEntity entity) {
        Long id = entity.getId();
        entity.fillRandom();
        entity.setId(id);

        return entity;
    }

    private TestDaoEntity getRandomEntityUpdated() {
        TestDaoEntity entity = getRandomEntity();

        return getRandomEntityUpdated(entity);
    }

    @Getter
    static class TestJpaDAOImpl extends AbstractTypedJpaDAOImpl<TestDaoEntity> {
        private EntityManager entityManager;

        public TestJpaDAOImpl() {
            super(TestDaoEntity.class);
            entityManager = Persistence.createEntityManagerFactory(Parameters.PU_NAME)
                                       .createEntityManager();
        }

        @Override
        public EntityManager getEntityManager() {
            return entityManager;
        }
    }
}
